/*
 * DB2Query object defines the needed query functions for the dbConnect IBM DB2 driver
 * Copyright (C) 2003 Johnathan Ingram, jingram@rogueware.org
 *
 * This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Lesser General Public
 *   License as published by the Free Software Foundation; either
 *   version 2.1 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public
 *   License along with this library; if not, write to the Free Software
 *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  US
 *
 */


#include "ibmDB2Query.h"

// -----------------------------------------------------------------------------
// PRIVATE:
// -----------------------------------------------------------------------------


//------------------------------------------------------------------------------
// DB2Query::_db2GetFieldsInformation
//------------------------------------------------------------------------------
void 
DB2Query::_db2GetFieldsInformation()
{
   // Clear any previouse field information just incase.
   _freeCollection(FIELD_INFORMATION);

   // Internal method used to build the field information.
   
   // Allocate the information pointers
   _numFieldInformation = _fieldCount;
   _fieldInformation = (DB2FieldDescription**)malloc(_numFieldInformation * sizeof(DB2FieldDescription*));
   
   // Assign the data to the corresponding fields.
   for (int i=0; i<_numFieldInformation; i++)
   {
      // Get the field information
      SQLRETURN cliRC;
      SQLCHAR colName[256];
      SQLSMALLINT colNameLen;
      SQLSMALLINT colType;
      SQLUINTEGER colSize;
      SQLSMALLINT colScale;
      SQLSMALLINT colPrecision;
      SQLSMALLINT colNullable;
      SQLSMALLINT colAutoUnique;
               
      // Obtain the required information
      cliRC = SQLDescribeCol(_hstmt, (SQLSMALLINT)i+1,
                             colName, sizeof(colName)-1, &colNameLen,
                             &colType, &colSize, &colScale, &colNullable);

      if (cliRC == SQL_SUCCESS)
         cliRC = SQLColAttribute(_hstmt, (SQLSMALLINT)i+1, SQL_DESC_PRECISION, NULL, 0, NULL, &colPrecision);

      if (cliRC == SQL_SUCCESS)
         cliRC = SQLColAttribute(_hstmt, (SQLSMALLINT)i+1, SQL_DESC_AUTO_UNIQUE_VALUE, NULL, 0, NULL, &colAutoUnique);
            
      // Finally bail on an error      
      if (cliRC != SQL_SUCCESS)
      {
         SQLCHAR sqlState[10];
         SQLINTEGER sqlCode;
         SQLCHAR message[255];
         SQLSMALLINT length;
      
         SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
         string err = "_db2GetFieldsInformation(): ";      
         err += (const char*)message;
           
         throw ErrorQuerying(err);
      }
                          
      // Work out the field type.
      FieldType _type = _db2ResolveFieldType(colType);
      
      // Field Properties.      
      string fName = (const char*)colName;
      
      long int  _scale = colScale;
      
      long int  _precision = 0;
      if (colType == SQL_DECIMAL || colType == SQL_NUMERIC || colType == SQL_DOUBLE ||
          colType == SQL_FLOAT || colType == SQL_INTEGER || colType == SQL_REAL || colType == SQL_SMALLINT)
         _precision = colPrecision;

      bool     _isIncrement = (colAutoUnique == SQL_FALSE ? false : true);  // Will always return false. Future versions of CLI will support this.
      bool     _isPriKey    = false;    // Not supported 
      bool     _isUnique    = false;    // Not supported 
      bool     _isNotNull   = (colNullable == SQL_NULLABLE ? false : true);
     
      // Add the field properties to the vector.
      _fieldInformation[i] = new DB2FieldDescription(fName, i, _type, _isIncrement, _isPriKey, _isUnique, 
                                                     _isNotNull, _precision, _scale, colType, colSize);
   }
}  // DB2Query::_db2GetFieldsInformation


//------------------------------------------------------------------------------
// DB2Query::_db2GetResultSetRow
//------------------------------------------------------------------------------
void
DB2Query::_db2GetResultSetRow()
{
   // NOTE: This function always assumes fetchNext has been called before the start   
   // Clear any previouse field information just incase.
   _freeCollection(FIELD_VALUES);
   

   // Allocate the value pointers
   _numRecordValues = _fieldCount;
   _recordValues = (DB2Value**)malloc(_numRecordValues * sizeof(DB2Value*));

   
   // Get the data :)  
   SQLRETURN cliRC; 
   for (int i=0; i<_fieldCount; i++)
   {
      // Allocate the field
      _recordValues[i] = new DB2Value(_fieldInformation[i]->name().c_str());
      
      // Get the field value according to the field type
      SQLINTEGER strlen_indptr, tmp_strlen_indptr;
      void *buffer = NULL;
      long  bufferSize = 0;
      void *bufferOffset = NULL;

      JDate dVal(0.0);   // Epoch
      
      switch(_fieldInformation[i]->db2FieldType())
      {         
         // Obtain the data as an 64bit integer value            
         case SQL_BIGINT:
         case SQL_TINYINT:
         case SQL_INTEGER:
         case SQL_SMALLINT:
            bufferSize = sizeof(DBLONG);
            buffer = malloc(bufferSize);
         
            cliRC = SQLGetData(_hstmt, i+1, SQL_C_SBIGINT, buffer, bufferSize, &strlen_indptr); 
            if (cliRC == SQL_SUCCESS)
            {
               if (strlen_indptr == SQL_NULL_DATA)
                  _recordValues[i]->setNULL();
               else               
                  _recordValues[i]->setLong(*(DBLONG*)buffer);
            }
            break;       
         
            
         // Obtain the data as an double floating point value            
         case SQL_DECIMAL:
         case SQL_DOUBLE:
         case SQL_FLOAT:
         case SQL_NUMERIC:
         case SQL_REAL:
            bufferSize = sizeof(double);
            buffer = malloc(bufferSize);
         
            cliRC = SQLGetData(_hstmt, i+1, SQL_C_DOUBLE, buffer, bufferSize, &strlen_indptr); 
            if (cliRC == SQL_SUCCESS)
            {
               if (strlen_indptr == SQL_NULL_DATA)
                  _recordValues[i]->setNULL();
               else               
                  _recordValues[i]->setFloat(*(double*)buffer);
            }
            break;       
         
            
         // Obtain the data as character data (Allocate enough for field size)
         case SQL_CHAR:
         case SQL_CLOB:
         case SQL_VARCHAR:
         case SQL_GRAPHIC:
         case SQL_VARGRAPHIC:
            bufferSize = _fieldInformation[i]->db2FieldSize() + 1;  // +1 for NULL terminating char
            buffer = malloc(bufferSize);
            memset(buffer, 0, bufferSize);
         
            cliRC = SQLGetData(_hstmt, i+1, SQL_C_CHAR, buffer, bufferSize, &strlen_indptr); 
            if (cliRC == SQL_SUCCESS)
            {
               if (strlen_indptr == SQL_NULL_DATA)
                  _recordValues[i]->setNULL();
               else               
                  _recordValues[i]->setString((char*)buffer);
            }
            break;                
           
            
         // Obtain the data as timstamp value
         case SQL_TYPE_DATE:
         case SQL_TYPE_TIME:
         case SQL_TYPE_TIMESTAMP:
            bufferSize = sizeof(TIMESTAMP_STRUCT);
            buffer = malloc(bufferSize);
         
            cliRC = SQLGetData(_hstmt, i+1, SQL_C_TYPE_TIMESTAMP, buffer, bufferSize, &strlen_indptr); 
            if (cliRC == SQL_SUCCESS)
            {
               if (strlen_indptr == SQL_NULL_DATA)
                  _recordValues[i]->setNULL();
               else               
               {
                  // Build a JDate from the components
                  dVal.setDate(((TIMESTAMP_STRUCT*)buffer)->year,
                               ((TIMESTAMP_STRUCT*)buffer)->month,
                               ((TIMESTAMP_STRUCT*)buffer)->day,
                               ((TIMESTAMP_STRUCT*)buffer)->hour,
                               ((TIMESTAMP_STRUCT*)buffer)->minute,
                               ((TIMESTAMP_STRUCT*)buffer)->second );
                  _recordValues[i]->setDateTime(dVal);
               }
            }
            break;
         

         // Obtain the data as binary value (Obtain the data block by block)
         case SQL_BLOB:
         case SQL_BINARY:
            bufferSize = FETCH_BLOCK_SIZE;
            buffer = malloc(bufferSize);
         
            cliRC = SQLGetData(_hstmt, i+1, SQL_C_BINARY, buffer, bufferSize, &strlen_indptr); 
            if (cliRC == SQL_SUCCESS || cliRC == SQL_SUCCESS_WITH_INFO)
            {
               if (strlen_indptr == SQL_NULL_DATA)
                  _recordValues[i]->setNULL();
               else
               {
                  // Get any left over data in a single query. Result is set to SQL_SUCCESS_WITH_INFO 
                  //   if there is still data left to be read
                  if (cliRC == SQL_SUCCESS_WITH_INFO)
                  {
                     // strlen_indptr now has the total size of the data in the column
                     tmp_strlen_indptr = strlen_indptr;
                     
                     buffer = realloc(buffer, tmp_strlen_indptr);
                     bufferOffset = (void*)((unsigned int)buffer + bufferSize);
                     
                     cliRC = SQLGetData(_hstmt, i+1, SQL_C_BINARY, bufferOffset, tmp_strlen_indptr - bufferSize, &strlen_indptr); 
                     if (cliRC != SQL_SUCCESS)
                        break;
                     
                     // Now finally set the buffer size
                     bufferSize = tmp_strlen_indptr;
                  }
                  else
                  {
                     // The buffer size is the number of bytes read
                     bufferSize = strlen_indptr;
                  }
                  if (cliRC == SQL_SUCCESS)
                     _recordValues[i]->setBinary(buffer, bufferSize);
               }
            }
            break;       
                     
            
         default:
            char buf[20];
            memset(buf, 0, sizeof(buf));
            snprintf(buf, sizeof(buf)-1, "%d", _fieldInformation[i]->db2FieldType());
            
            string err = "_db2GetResultSetRow(): Unknown DB2 SQL field type ";         
            err += buf;
            throw ResultSetError(err);
            break;
      }

      // Free the buffer
      if (buffer)
      {
         free(buffer);
         buffer = NULL;         
      }
      
      // Make sure fetching the field data was ok.      
      if (cliRC != SQL_SUCCESS)
      {
         SQLCHAR sqlState[10];
         SQLINTEGER sqlCode;
         SQLCHAR message[255];
         SQLSMALLINT length;
      
         SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
         string err = "_db2GetResultSetRow(): Unable to obtain field data, ";
         err += (const char*)message;
           
         throw ResultSetError(err);
      }
   }
   
   
   // Pre-fetch the next row
   cliRC = SQLFetch(_hstmt);
   
   // Check if the previouse fetchNext fetched the last row   
   if (cliRC == SQL_NO_DATA_FOUND)
   {
      _eof = true;
   }
   else if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
   
      SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
   
      string err = "_db2GetResultSetRow(): ";      
      err += (const char*)message;
        
      throw ResultSetError(err);
   }   
}  // DB2Query::_db2GetResultSetRow


//------------------------------------------------------------------------------
// DB2Query::_db2ResolveFieldType
//------------------------------------------------------------------------------
FieldType
DB2Query::_db2ResolveFieldType(
   SQLSMALLINT type)
{
   FieldType res;
   
   // LONG types are not supported as they should be replaced by LOB types according to the DB2 documentation
   
   // TODO: Research and support the following types
   //       SQL_BIT, SQL_DATALINK
   //       SQL_DB_CLOB, SQL_DB_LOCATOR, SQL_WCHAR
   // TODO: Support VARBINARY
   //case SQL_VARBINARY:
   //   res = FT_BLOB;
   //   break;      
   
   switch(type)
   {      
      case SQL_BIGINT:
         res = FT_LONG;
         break;
      case SQL_BLOB:
         res = FT_BLOB;
         break;
      case SQL_CHAR:
         res = FT_STRING;
         break;
      case SQL_TINYINT:
         res = FT_LONG;
         break;
      case SQL_BINARY:
         res = FT_BLOB;
         break;
      case SQL_CLOB:
         res = FT_STRING;
         break;
      case SQL_TYPE_DATE:
         res = FT_DATETIME;
         break;
      case SQL_DECIMAL:
         res = FT_DOUBLE;
         break;
      case SQL_DOUBLE:
         res = FT_DOUBLE;
         break;
      case SQL_FLOAT:
         res = FT_DOUBLE;
         break;
      case SQL_INTEGER:
         res = FT_LONG;
         break;
      case SQL_NUMERIC:
         res = FT_DOUBLE;
         break;
      case SQL_REAL:
         res = FT_DOUBLE;
         break;
      case SQL_SMALLINT:
         res = FT_SHORT;
         break;
      case SQL_TYPE_TIME:
         res = FT_DATETIME;
         break;
      case SQL_TYPE_TIMESTAMP:
         res = FT_DATETIME;
         break;
      case SQL_VARCHAR:
         res = FT_STRING;
         break;
      case SQL_GRAPHIC:
         res = FT_STRING;
         break;      
      case SQL_VARGRAPHIC:
         res = FT_STRING;
         break;      
      default:
         res = FT_UNKNOWN;
         break;      
   }
   
   return res;
}  // DB2Query::_db2ResolveFieldType


//------------------------------------------------------------------------------
// DB2Query::_db2ParseBindParameters
//------------------------------------------------------------------------------
string
DB2Query::_db2ParseBindParameters(
      const string& originalSqlStatement,
      vector<DB2BindParam*> &bindParameters)
{
   // Replace the ":name" parameters with a ? from left to right building the bindParameter vector to point to the correct values
   // Try and substitute the parameters checking to make sure they exist.
   string res = originalSqlStatement;    
   int pos, endPos;
   char paramName[1024];
   
   pos = res.find(":");
   while (pos != string::npos)
   {
      memset(paramName, 0, sizeof(paramName));
      
      // Get the position at the end of the parameter name and determine the parameter name
      endPos = pos +1;
      char tstChar = res.c_str()[endPos];
      while ((tstChar >= '0' && tstChar <= '9') ||    // 0 - 9
             (tstChar >= 'A' && tstChar <= 'Z') ||    // A - Z
             (tstChar >= 'a' && tstChar <= 'z')||     // a - z
             (tstChar == '_') )                       // _              
      {
         endPos++;         
         if (endPos > res.length())
            throw BindParameterError("_db2ParseBindParameters(): Unable to properly parse the bind parameters");
         
         tstChar = res.c_str()[endPos];
      }
            
      char *paramNameStart = (char*)((int)res.c_str() + pos+1);   // Jump the : in the param name
      
      strncpy(paramName, paramNameStart, endPos - pos-1);
      // Replace the parameter with a ?      
      res.replace(pos, endPos-pos, "?");

      // Make sure the parameter name exists and add the value to the parameter vector
      bool found = false;
      for (int i =0; i<_numParameters; i++)
      {
         if (strcasecmp(_parameters[i]->name().c_str(), paramName) == 0)
         {
            found = true;
            bindParameters.push_back(_parameters[i]);
            break;
         }
      }
      
      if (!found)
      {
         string err = "_db2ParseBindParameters(): The bind parameter, ";
         err += paramName;
         err += ", in the SQL statement: ";
         err += originalSqlStatement;
         err += " has not been set.";
         throw BindParameterNotSet(err);
      }

      // Find the next parameter and continue
      pos = res.find(":");
   }
   
   return res;      
}  // DB2Query::_db2ParseBindParameters
   

//------------------------------------------------------------------------------
// DB2Query::_db2BindParameters
//------------------------------------------------------------------------------
void
DB2Query::_db2BindParameters(
      const string& resolvedSqlStatement,
      vector<DB2BindParam*> &bindParameters)
{
   SQLRETURN cliRC = SQL_SUCCESS;
   
   // Iterate through the pointer list and bind the parameter values according to type from left to right
   for (int i=0; i<bindParameters.size(); i++)
   {
      // Get the required parameter information      
      SQLSMALLINT  paramSQLType;
      SQLUINTEGER  columnSize;
      SQLSMALLINT  decDigits;
      SQLSMALLINT  nullableInfo;
      
      cliRC = SQLDescribeParam(_hstmt, i+1, &paramSQLType, &columnSize, &decDigits, &nullableInfo);
      if (cliRC != SQL_SUCCESS)
      {
         SQLCHAR sqlState[10];
         SQLINTEGER sqlCode;
         SQLCHAR message[255];
         SQLSMALLINT length;
         
         SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
         
         string err = "_db2BindParameters(): ";
         err += (const char*)message;              
         throw BindParameterError(err);
      }
      
      
      cliRC = SQLBindParameter(_hstmt, i+1, SQL_PARAM_INPUT, bindParameters[i]->getDB2CType(),
                               paramSQLType, columnSize, decDigits, bindParameters[i]->getDB2DataPointer(),
                               bindParameters[i]->getDB2DataPointerLength(), bindParameters[i]->getDB2StrLenIndPtr() );        
      if (cliRC != SQL_SUCCESS)
      {
         SQLCHAR sqlState[10];
         SQLINTEGER sqlCode;
         SQLCHAR message[255];
         SQLSMALLINT length;
         
         SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
         
         string err = "_db2BindParameters(): ";
         err += (const char*)message;              
         throw BindParameterError(err);
      }
   }
}  // DB2Query::_db2BindParameters


//------------------------------------------------------------------------------
// DB2Query::_freeCollection
//------------------------------------------------------------------------------
void 
DB2Query::_freeCollection(
   CollectionType type)
{
   int i;
   switch (type)
   {
      case FIELD_INFORMATION:
         if (_fieldInformation)
         {
            for (i=0; i<_numFieldInformation; i++)
            {
               if (_fieldInformation[i])
               {
                  delete _fieldInformation[i];
                  _fieldInformation[i] = NULL;         
               }
            }
            free(_fieldInformation);
            _fieldInformation = NULL;
            _numFieldInformation = 0;
            _fieldCount = 0;
         }
      
         break;      
      
      case FIELD_VALUES:
         if (_recordValues)
         {
            for (i=0; i<_numRecordValues; i++)
            {
               if (_recordValues[i])
               {
                  delete _recordValues[i];
                  _recordValues[i] = NULL;         
               }
            }
            free(_recordValues);
            _recordValues = NULL;
            _numRecordValues = 0;
         }
       
         break;
         
      case BIND_PARAMETERS:
         if (_parameters)
         {
            for (i=0; i<_numParameters; i++)
            {
               if (_parameters[i])
               {
                  delete _parameters[i];
                  _parameters[i] = NULL;         
               }
            }
            free(_parameters);
            _parameters = NULL;
            _numParameters = 0;
         }
               
         break;
   }
}  // DB2Query::_freeCollection


// -----------------------------------------------------------------------------
// PUBLIC:
// -----------------------------------------------------------------------------


//------------------------------------------------------------------------------
// DB2Query::DB2Query
//------------------------------------------------------------------------------
DB2Query::DB2Query(
   DB2Connection* parentConnection,
   int                   index)
: 
   _parentConnection(parentConnection),
   _index(index),
   _isTransaction(false),
   _hstmt(SQL_NULL_HSTMT),
   _db2CurrentRow(0),
   _recordValues(NULL),
   _numRecordValues(0),
   _fieldInformation(NULL),
   _numFieldInformation(0),
   _parameters(NULL),
   _numParameters(0)
{

}  // DB2Query::DB2Query


//------------------------------------------------------------------------------
// DB2Query::~DB2Query
//------------------------------------------------------------------------------
DB2Query::~DB2Query()
{   
   // If there is still a transaction, rollback
   if (_isTransaction)
      rollback();
   
   //Make sure all DB2 resources are released
   if (_hstmt != SQL_NULL_HSTMT)
   {
      SQLFreeHandle(SQL_HANDLE_STMT, _hstmt);
      _hstmt = SQL_NULL_HSTMT;
   }

   // Free any parameters
   _freeCollection(BIND_PARAMETERS);
   
   // Free the current field values
   _freeCollection(FIELD_VALUES);
  
   // Clear any previouse field information.
   _freeCollection(FIELD_INFORMATION);

   // Release this connection from the parent connection object.
   _parentConnection->releaseQueryConnection(this);
}  // DB2Query::~DB2Query


//------------------------------------------------------------------------------
// DB2Query::clearBindParams
//------------------------------------------------------------------------------
void 
DB2Query::clearBindParams()
{
   // Free all the bind parameters.
   _freeCollection(BIND_PARAMETERS);
}  // DB2Query::clearBindParams


//------------------------------------------------------------------------------
// DB2Query::command
//------------------------------------------------------------------------------
void
DB2Query::command(
   const string& sqlStatement)
{
   // Clear any bind parameters as we now have a new query
   clearBindParams();
   
   // Check if this is a function been called and build the correct sql if it is
   //string newSqlStatement = _pgsqlParseFunctionCall(sqlStatement);
   string newSqlStatement = sqlStatement;
   // TODO: Add procedure calling "CALL XYZ(?,?)"
   
   // Call the base query to store a copy of the query.
   BaseQuery::command(newSqlStatement);
} // DB2Query::command


//------------------------------------------------------------------------------
// DB2Query::bindParam
//------------------------------------------------------------------------------
BaseValue*
DB2Query::bindParam(
   const string& paramName)
{
   // Make sure the name has not already been added. If it has, return the instance to it
   for (int i=0; i<_numParameters; i++)
      if (strcasecmp(_parameters[i]->bindName.c_str(), paramName.c_str()) == 0)
         return _parameters[i];
  
   // Make sure the parameter is present in the quey
   if (!_isBindParameterPresent(paramName))
   {
      string err = "bindParam(): The specified bind parameter, ";
      err += paramName;
      err += ", is not present in the SQL statement: ";
      err += _sqlStatement;      
      throw BindParameterNotPresent(err);
   }      
      
   // Add the value to the parameters array.
   _numParameters++;
   _parameters = (DB2BindParam**)realloc((void*)_parameters, _numParameters * sizeof(DB2BindParam*));
   _parameters[_numParameters-1] = new DB2BindParam(paramName);
   return _parameters[_numParameters-1];
}  // DB2Query::bindParam


//------------------------------------------------------------------------------
// DB2Query::execute
//------------------------------------------------------------------------------
void
DB2Query::execute()
{
   // Holds pointers for the bind values (left to right alinged to the ?'s that will be substituted)
   vector<DB2BindParam*> bindParameters;   
   
   SQLRETURN cliRC = SQL_SUCCESS;
   
   //Make sure all DB2 resources are released
   if (_hstmt != SQL_NULL_HSTMT)
   {
      SQLFreeHandle(SQL_HANDLE_STMT, _hstmt);
      _hstmt = SQL_NULL_HSTMT;
   }
   _queryParameterNames.clear();
   
   // Number of rows in the result set is now 0
   _db2CurrentRow = 0;
   
   // Clear any previouse field information.
   _freeCollection(FIELD_INFORMATION);
 
   // Free the current field values
   _freeCollection(FIELD_VALUES);

   
   // Replace all the bind parameters in the SQL with '?' characters and build name list for correct binding
   string resolvedSqlStatement = _db2ParseBindParameters(_sqlStatement, bindParameters);
   
   // Ping the connection to make sure it is still valid.
   _parentConnection->_db2Ping(_index);
      
   // Allocate the statement handle
   cliRC = SQLAllocHandle(SQL_HANDLE_STMT, _parentConnection->_handles[_index]->_hdbc, &_hstmt);
   if (cliRC != SQL_SUCCESS)
      throw ErrorQuerying("execute(): Unable to allocate the statement handle.");
   
   // Prepare the statement 
   cliRC = SQLPrepare(_hstmt, (SQLCHAR*)resolvedSqlStatement.c_str(), SQL_NTS);
   if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
      
      SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
      string err = "execute(): ";
      err += (const char*)message;           
      throw ErrorQuerying(err);
   }
     
   // Always make sure there is a transaction that the query is occuring in
   // Call this after the SQLPrepare as SQLPrepare will create a transaction
   if (!_isTransaction)
      transBegin();

   // Bind the parameters
   _db2BindParameters(resolvedSqlStatement, bindParameters);
   
   // Execute the query
   cliRC = SQLExecute(_hstmt);
   // Make sure it executed correctly
   if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
      
      SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
      string err = "execute(): ";      
      err += (const char*)message;
           
      throw ErrorQuerying(err);
   }

   // Check if there is a result set
   SQLSMALLINT nResultCols;
   cliRC = SQLNumResultCols(_hstmt, &nResultCols);
   if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
      
      SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
      string err = "execute(): ";      
      err += (const char*)message;
           
      throw ErrorQuerying(err);
   }


   // Handle the result set comming back
   if (nResultCols)
   {
      // We have a result set with data      
      _fieldCount = nResultCols;

      // Get the field information for this query.
      _db2GetFieldsInformation();

      _eof = false;
      
      // Pre-fetch the first row making sure there is data in the result set
      cliRC = SQLFetch(_hstmt);
      if (cliRC == SQL_NO_DATA_FOUND)
      {
         _eof = true;
      }
      else if (cliRC != SQL_SUCCESS)
      {
         SQLCHAR sqlState[10];
         SQLINTEGER sqlCode;
         SQLCHAR message[255];
         SQLSMALLINT length;
      
         SQLGetDiagRec(SQL_HANDLE_STMT, _hstmt, 1, sqlState, &sqlCode, message, 250, &length);
      
         string err = "execute(): ";      
         err += (const char*)message;
           
         throw ResultSetError(err);
      }            
   }
   else
   {
      // We have no records, could be an update or something
      _fieldCount = 0;
      _eof = true;            
   }
}  // DB2Query::execute


//------------------------------------------------------------------------------
// DB2Query::next
//------------------------------------------------------------------------------
void
DB2Query::fetchNext()
{
   // Do nothing if the we have reached the end of the record set.
   if (_eof)
      return;
   
   // Get the data. _db2GetResultSetRow will set the _eof parameter 
   _db2GetResultSetRow();
   _db2CurrentRow++;   
}  // DB2Query::next


//------------------------------------------------------------------------------
// DB2Query::transBegin
//------------------------------------------------------------------------------
void
DB2Query::transBegin()
{
   // Make sure a transaction is not already active
   if (_isTransaction)
      throw TransactionError("transBegin(): "
         "A transaction is already active. Commit or rollback the transaction before creating a new transaction.");

	_isTransaction = true;
   
   /*
    * A DB2 transaction is automatically started when a SQLPrepare, SQLExecDirect or SQLGetTypeInfo call is made.
    * Hence a transaction is implied, and does not need to be explicilty created.      
    * This function should remain as a token function with basic error checking, Johnathan Ingram
    *
    */      
}  // DB2Query::transBegin
   
   
//------------------------------------------------------------------------------
// DB2Query::commit
//------------------------------------------------------------------------------
void
DB2Query::commit()
{
   // Make sure a transaction is active
   if (!_isTransaction)
      throw TransactionError("commit(): "
         "A transaction is not active. Create a transaction before calling commit.");
   
   // Commit the transaction
   SQLRETURN cliRC = SQL_SUCCESS;
   cliRC = SQLEndTran(SQL_HANDLE_DBC, _parentConnection->_handles[_index]->_hdbc, SQL_COMMIT);
   
   // Check if an error occured while commiting
   if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
      
      SQLGetDiagRec(SQL_HANDLE_DBC, _parentConnection->_handles[_index]->_hdbc, 1, sqlState, &sqlCode, message, 250, &length);
      
      string err = "commit(): ";      
      err += (const char*)message;
           
      throw TransactionError(err);
   }
   
	_isTransaction = false;   
}  // DB2Query::commit
   
  
//------------------------------------------------------------------------------
// DB2Query::rollback
//------------------------------------------------------------------------------
void
DB2Query::rollback()
{
   // Make sure a transaction is active
   if (!_isTransaction)
      throw TransactionError("rollback(): "
         "A transaction is not active. Create a transaction before calling rollback.");

   // Roolback the transaction
   SQLRETURN cliRC;
   cliRC = SQLEndTran(SQL_HANDLE_DBC, _parentConnection->_handles[_index]->_hdbc, SQL_ROLLBACK);
   
   // Check if an error occured while rolling back
   if (cliRC != SQL_SUCCESS)
   {
      SQLCHAR sqlState[10];
      SQLINTEGER sqlCode;
      SQLCHAR message[255];
      SQLSMALLINT length;
      
      SQLGetDiagRec(SQL_HANDLE_DBC, _parentConnection->_handles[_index]->_hdbc, 1, sqlState, &sqlCode, message, 250, &length);
      
      string err = "rollback(): ";      
      err += (const char*)message;
           
      throw TransactionError(err);
   }
   
	_isTransaction = false;      
}  // DB2Query::rollback


//------------------------------------------------------------------------------
// DB2Query::getFieldInfoByColumn
//------------------------------------------------------------------------------
BaseFieldDescription*
DB2Query::getFieldInfoByColumn(
   int index)
{
   // Make sure the index is in range.
   if (index < 0 || index >= _numFieldInformation)
      throw IndexOutOfRange("getFieldInfoByColumn(): The field index is out of range for the current result set");

   return _fieldInformation[index];      
}  // DB2Query::getFieldInfoByColumn


//------------------------------------------------------------------------------
// DB2Query::getFieldInfoByName
//------------------------------------------------------------------------------
BaseFieldDescription*
DB2Query::getFieldInfoByName(
   const string& fieldName)
{
   // Try and find the field name
   for (int i=0; i<_numFieldInformation; i++)
      if (strcasecmp(_fieldInformation[i]->name().c_str(), fieldName.c_str()) == 0)
         return _fieldInformation[i];

   throw NameNotFound("getFieldInfoByName(): The field name was not found for the current result set.");   
}  // DB2Query::getFieldInfoByName


//------------------------------------------------------------------------------
// DB2Query::getFieldByColumn
//------------------------------------------------------------------------------
BaseValue*
DB2Query::getFieldByColumn(
   int index)
{
   // Make sure the index is in range.
   if (index < 0 || index >= _numRecordValues)
   {
      throw IndexOutOfRange("getFieldByColumn(): The field index is out of range for the current result set");
   }

   return _recordValues[index];      
}  // DB2Query::getFieldByColumn
   

//------------------------------------------------------------------------------
// DB2Query::getFieldByName
//------------------------------------------------------------------------------
BaseValue*
DB2Query::getFieldByName(
   const string& fieldName)
{
   // Try and find the field name
   for (int i=0; i<_numRecordValues; i++)
      if (strcasecmp(_recordValues[i]->name().c_str(), fieldName.c_str()) == 0)
         return _recordValues[i];

   throw NameNotFound("getFieldByName(): The field name was not found for the current result set.");   
}  // DB2Query::getFieldByName
